package com.gc.ip;

import java.io.Serializable;
import java.util.Arrays;


@SuppressWarnings("serial")
public class IPAddress implements Serializable
{
	/** Raw string ip address **/
	private String strAddr;
	
	/** Prefix Length. A negative number indicates no prefixLength. **/
	private int prefixLength;
	
	/** Subnet Mask. This field is non-null only when user set it explicitly. **/
	private String subnetMask;
	
	/** cached address byte array from the raw strAddr **/
	private byte[] bytesAddr;
	
	public IPAddress(String ipAddressStr, String subnetMask)
	{
		this.setStrAddr(ipAddressStr);
		this.setSubnetMask(subnetMask);		
	}
	
	public IPAddress(String ipAddressStr, int prefixLength)
	{
		this.setStrAddr(ipAddressStr);
		
		this.prefixLength = prefixLength;
		this.subnetMask = null;
	}
	
	public IPAddress(String ipAddressStr)
	{
		this(ipAddressStr, -1);
	}
	
	public IPAddress()
	{
		this(null, -1);
	}
	
	public void reset()
	{
		this.strAddr = null;
		this.prefixLength = -1;
		
		this.bytesAddr = null;
	}
	
	private void updateInetAddress()
	{
		if(strAddr == null || strAddr.isEmpty())
		{
			bytesAddr = null;
			return;
		}
		bytesAddr = IPUtils.toIPBytes(strAddr);
	}
	/**
	 * Get the IP type
	 * @return
	 */
	public IPType getIPType()
	{
		if(this.bytesAddr == null)
		{
			return IPType.NONE;
		}
		
		//IPv4
		if(validateIP(bytesAddr, IPUtils.IPV4_BYTE_SIZE, prefixLength))
		{
			return IPType.IPv4;
		}
		//IPv6
		else if(validateIP(bytesAddr, IPUtils.IPV6_BYTE_SIZE, prefixLength))
		{
			return IPType.IPv6;
		}
		return IPType.NONE;
	}
	
	public boolean isValid()
	{
		IPType ipType = getIPType();
		return ipType == IPType.IPv4 || ipType == IPType.IPv6;
	}
	
	private boolean validateIP(byte[] b, int byteSize, int prefix)
	{
		if(b.length == byteSize)
		{
			//No need to check
			if(prefix < 0)
			{
				return true;
			}
			//make sure prefix is not out of range
			if(prefix >= 0 && prefix <= byteSize * 8)
			{
				return true;
			}
		}
		return false;
	}
	
	public byte[] getBytesAddr()
	{
		return bytesAddr;
	}

	/**
	 * @return the strAddr
	 */
	public String getStrAddr()
	{
		return strAddr;
	}

	/**
	 * @param strAddr the strAddr to set
	 */
	public void setStrAddr(String strAddr)
	{
		this.strAddr = strAddr;
		
		updateInetAddress();
	}

	/**
	 * @return the prefixLength
	 */
	public int getPrefixLength()
	{
		return prefixLength;
	}

	/**
	 * NOTE: the field subnetMask won't be updated by prefixLength.
	 * This is used for checking if the subnetMask is set explicitly
	 * 
	 * To get the subnetMask, use one of the below methods
	 * 1. IPUtils.toIPv4MaskString(getPrefixLength())
	 * 2. IPUtils.toIPv6MaskString(getPrefixLength())
	 * 
	 * @param prefixLength the prefixLength to set
	 */
	public void setPrefixLength(int prefixLength)
	{
		this.prefixLength = prefixLength;
		this.subnetMask = null;
	}
	/**
	 * The prefix length field will automatically be updated
	 * 
	 * @param subnetMask the subnetMask to set
	 */
	public void setSubnetMask(String subnetMask)
	{
		this.subnetMask = subnetMask;
		
		if(this.subnetMask == null || this.subnetMask.isEmpty() || this.bytesAddr == null)
		{
			this.prefixLength = -1;
			return;
		}
		
		if(this.bytesAddr.length == IPUtils.IPV4_BYTE_SIZE)
		{
			this.prefixLength = IPUtils.toIPv4PrefixLength(this.subnetMask);
		}
		else
		{
			this.prefixLength = IPUtils.toIPv6PrefixLength(this.subnetMask);
		}
	}
	
	public String validIPString()
	{
		IPType ipType = getIPType();
		if(ipType == IPType.IPv4 || ipType == IPType.IPv6)
		{
			return toString();
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode()
	{
		int h = 1;
		h = h * 31 + Arrays.hashCode(bytesAddr);
		h = h * 31 + prefixLength;
		return h;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj)
	{
		if(this == obj)
		{
			return true;
		}
		
		if(!(obj instanceof IPAddress))
		{
			return false;
		}
		IPAddress other = (IPAddress) obj;
		if(!Arrays.equals(bytesAddr, other.bytesAddr))
		{
			return false;
		}
		if(prefixLength != other.prefixLength)
		{
			return false;
		}
		return true;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		if(strAddr != null)
		{
			sb.append(strAddr);
		}
		if(subnetMask != null)
		{
			sb.append('/').append(subnetMask);
		}
		else if(prefixLength >= 0)
		{
			sb.append('/').append(prefixLength);
		}
		return sb.toString();
	}
}
